package cn.sciento.transfer.infra.data.check.store.impl;

import cn.sciento.core.exception.CommonException;import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.codec.digest.DigestUtils;
import cn.sciento.transfer.infra.data.check.constant.SerializeType;
import cn.sciento.transfer.infra.data.check.mapping.MappedFile;
import cn.sciento.transfer.infra.data.check.mapping.MappedIndex;
import cn.sciento.transfer.infra.data.check.mapping.MappedRow;
import cn.sciento.transfer.infra.data.check.store.DataStoreService;
import org.springframework.stereotype.Service;

@Service
public class DataStoreServiceImpl implements DataStoreService {
  public static final String INDEX_FILE_PREFIX = System.getProperty("hdtt.data-check.index", 
      
      System.getProperty("user.home"));
  
  public static final String ROW_FILE_PREFIX = System.getProperty("hdtt.data-check.row", 
      
      System.getProperty("user.home"));
  
  public void saveRow(MappedRow mappedRow, String fileName) {
    MappedFile mappedFile = new MappedFile(builderRowFilePath(fileName), Long.valueOf(0L), mappedRow.calLength());
    Long offset = mappedFile.appendContent(mappedRow.encode());
    byte[] digit = DigestUtils.md5(mappedRow.encode().array());
    MappedIndex mappedIndex = new MappedIndex(mappedRow.findId(), digit, offset);
    MappedFile mappedIndexFile = new MappedFile(builderIndexFilePath(fileName), Long.valueOf(0L), mappedRow.calLength());
    mappedIndexFile.appendContent(mappedIndex.encode());
  }
  
  public void batchSaveRow(List<MappedRow> mappedRows, String fileName) {
    MappedFile mappedFile = new MappedFile(builderRowFilePath(fileName), Long.valueOf(0L), 0);
    MappedFile mappedIndexFile = new MappedFile(builderIndexFilePath(fileName), Long.valueOf(0L), 0);
    try {
      for (MappedRow mappedRow : mappedRows) {
        Long offset = mappedFile.appendContentWithOutClose(mappedRow.encode());
        byte[] digit = DigestUtils.md5(mappedRow.encode().array());
        MappedIndex mappedIndex = new MappedIndex(mappedRow.findId(), digit, offset);
        mappedIndexFile.appendContentWithOutClose(mappedIndex.encode());
      } 
    } finally {
      mappedFile.close();
      mappedIndexFile.close();
    } 
  }
  
  public MappedRow searchAndCheckIndexByIndex(MappedIndex mappedIndex, String fileName) {
    MappedFile mappedIndexFile = new MappedFile(builderIndexFilePath(fileName), Long.valueOf(0L), mappedIndex.calLength() * 1000);
    Long page = Long.valueOf(0L);
    int size = mappedIndex.calLength() * 1000;
    try {
      ByteBuffer buffer = null;
      while ((buffer = mappedIndexFile.readByteBufferWithOutClose(page, size)).hasRemaining()) {
        Long long_1 = page, long_2 = page = Long.valueOf(page.longValue() + 1L);
        while (buffer.hasRemaining()) {
          byte[] indexBytes = new byte[mappedIndex.calLength()];
          buffer.get(indexBytes, 0, indexBytes.length);
          MappedIndex findIndex = mappedIndex.decode(indexBytes);
          if (Arrays.equals(mappedIndex.getMd5Digit(), findIndex.getMd5Digit()))
            return searchRowByOffset(Long.valueOf(findIndex.getOffset()), fileName); 
        } 
      } 
    } finally {
      mappedIndexFile.close();
    } 
    return null;
  }
  
  public <K, V> Map<K, V> mappedAllIndexToMap(String fileName, DataStoreService.IndexPredicate<K, V> predicate) {
    return mappedBlockIndexToMap(fileName, predicate, Long.valueOf(0L), 0);
  }
  
  public void deleteFile(String fileName) {
    MappedFile row = new MappedFile(builderRowFilePath(fileName), Long.valueOf(0L), 0);
    row.deleteFile();
    MappedFile index = new MappedFile(builderIndexFilePath(fileName), Long.valueOf(0L), 0);
    index.deleteFile();
  }
  
  public MappedRow searchRowByOffset(Long offset, String fileName) {
    MappedFile mappedRowFile = new MappedFile(builderRowFilePath(fileName), offset, 100);
    ByteBuffer rowHeader = mappedRowFile.readByteBufferWithOutClose(Long.valueOf(0L), 4);
    int header = rowHeader.getInt();
    byte headerMagic = (byte)(header >>> 24 & 0xFF);
    if (SerializeType.valueOf(headerMagic) != SerializeType.DATA)
      throw new CommonException("Index file does not match data file");
    int length = header & 0xFFFFFF;
    int bodySize = length - 4;
    ByteBuffer rowBody = mappedRowFile.readByteBuffer(Long.valueOf(4L), bodySize);
    return new MappedRow(rowBody.array());
  }
  
  public <K, V> Map<K, V> mappedBlockIndexToMap(String fileName, DataStoreService.IndexPredicate<K, V> predicate, Long offset, int size) {
    MappedFile mappedFile = new MappedFile(builderIndexFilePath(fileName), offset, size);
    try {
      MappedByteBuffer mappedByteBuffer = mappedFile.mappingFileInMemory();
      if (mappedByteBuffer == null)
        return null; 
      ByteBuffer readBuffer = mappedByteBuffer.asReadOnlyBuffer();
      MappedIndex testIndex = new MappedIndex(Long.valueOf(-1L), new byte[16], Long.valueOf(0L));
      int indexSize = testIndex.calLength();
      Map<K, V> result = new HashMap<>();
      while (readBuffer.hasRemaining()) {
        byte[] indexBytes = new byte[indexSize];
        readBuffer.get(indexBytes, 0, indexBytes.length);
        MappedIndex mappedIndex = testIndex.decode(indexBytes);
        result.put((K)predicate.findKey(mappedIndex), (V)predicate
            .findValue(mappedIndex));
      } 
      return result;
    } catch (IOException e) {
      return null;
    } finally {
      mappedFile.closeMemoryFile();
      mappedFile.close();
    } 
  }
  
  private String builderIndexFilePath(String fileName) {
    return INDEX_FILE_PREFIX + File.separator + "." + "index_" + fileName;
  }
  
  private String builderRowFilePath(String fileName) {
    return ROW_FILE_PREFIX + File.separator + "." + fileName;
  }
}
